# -*- encoding: utf-8 -*-
'''
@author: Dennis Keitzel, André Stannek
'''
import time
import cgi
import os
import mimetypes
import glob, urllib
from threading import Thread

from sqlalchemy import func, or_, select
from mutagen.id3 import ID3NoHeaderError 

import mediafile
import settings
from mediafile import Audiofile
from database import Database
from exceptions import BadRequestException
from settings_in_db import Settings
from helper import unique

db = Database()
revision = "$Rev: 1 $"

def handle(environ):
	'''Handle the query!'''
	starttime = time.time()
	try:
		query_params = cgi.parse_qs(environ["QUERY_STRING"])
		action = parse_query_params(query_params, 'action', entries=1)
		
		handler = get_handler(action)
		output = handler(query_params)
		
		output['status'] = 'ok'
	except BadRequestException, ex:
		output = {
			"status": "error",
			"message": ex.message
		}
	db.Session.remove()
	output['webservice revision'] = int(revision.split()[1])
	output["query response time"] = "%f ms" % ((time.time() - starttime) * 1000)
	return output

def get_handler(action):
	'''Return the specific handle function'''
	return ACTIONS.get(action, on_unknown_action)

def parse_query_params(query_params, param, entries=None, optional=False):
	'''Generalized function to parse query params.
	
	@param query_params: dict of all query params.
	@param param: param to parse.
	@param entries: possibility to specify the number of entries for the param.
	                raises an exception if too few or too many params are given.
	                standard behavior: parse all entries.
	@param optional: possibility to set the param optional if its not required (e.g. for regions).
	                 standard behavior: the param is not optional.
	
	Return the parsed entry if entries is set to 1, otherwise a list of entries.
	If the param is optional, a return of None can occur.
	'''
	
	result = None
	error_msg = 'Error while parsing param \'%s\'' % param
	query_param_entries = query_params.get(param, ())
	if query_param_entries:
		if entries: # save only the first 'entries' entries
			if entries < len(query_param_entries):
				raise BadRequestException('%s: too many params specified, only %s allowed here' % error_msg, entries)
			elif entries == 1: # don't build a list if there is only one entry
				result = query_param_entries[0]
			else:
				try:
					result = query_param_entries[:entries]
				except IndexError:
					raise BadRequestException('%s: not enough params specified, %s are needed here' % error_msg, entries)
		else: # save all entries
			result = query_param_entries
	else:
		if not optional:
			raise BadRequestException('%s: param not found, but needed' % error_msg)

	return result
	
def on_unknown_action(params):
	raise BadRequestException('unknown action specified')

def update_db(query_params=None):
	def update_thread(s):
		def store_status_in_db(s, new_added=0, deleted_removed=0, total_number_of_files=0, files_skipped=None, progress=None):
			s['update.new_added'] = new_added
			s['update.deleted_removed'] = deleted_removed
			s['update.total_number_of_files'] = total_number_of_files
			if files_skipped is not None:
				files_skipped = '|'.join(files_skipped)
			s['update.files_skipped'] = files_skipped
			s['update.progress'] = progress
			
		total_number_of_files = 0
		deleted_removed = 0
		new_added = 0
		files_skipped = []
		store_status_in_db(s, progress='working')
		
		# Remove deleted files from db
		for path in db.Session.query(Audiofile.id):
			if not os.access(path[0], os.F_OK):
				db.Session.delete(db.Session.query(Audiofile).filter_by(id=path).first())
				deleted_removed += 1
		
		# Add new files to db
		for files in os.walk(settings.BASE_PUBLIC_DATA_DIR):
			path = files[0]
			for file in files[2]:
				abspath = '%s/%s' % (path, file)
				if check_file_type(abspath):
					total_number_of_files += 1
					try:
						audiofile = mediafile.create_audiofile(abspath)
					except IOError, (errno, strerror): # Skip file if we have no read access
						files_skipped.append(('\'%s\' %s' % (abspath, strerror)).decode('utf-8', 'replace'))
						continue
					except ID3NoHeaderError, e: # Skip if file has no ID3 tags
						files_skipped.append(e.message.decode('utf-8', 'replace'))
						continue
					if not db.Session.query(Audiofile).filter_by(id=audiofile.id).first():
						db.Session.add(audiofile)
						new_added += 1
		
		store_status_in_db(s, new_added, deleted_removed, total_number_of_files, files_skipped, 'finished')
		db.Session.commit()
	
	s = Settings()
	if s.get('update.progress') == 'working':
		raise BadRequestException('Update process already started and hasn\'t finished yet')
	t = Thread(target=update_thread, args=(s,))
	t.start()
	return {'message':
		'update process startet, check progress via action=update_status'}
	
def update_db_status(query_params=None):
	s = Settings()
	try:
		files_skipped = s['update.files_skipped'].split('|')
	except (AttributeError, KeyError):
		files_skipped = None
	return {'update_status': {
		'new_added' : s.get('update.new_added'),
		'deleted_removed' : s.get('update.deleted_removed'),
		'total_number_of_files' : s.get('update.total_number_of_files'),
		'files_skipped' : files_skipped,
		'progress' : s.get('update.progress'),
		}}

def check_file_type(path):
    return mimetypes.guess_type(urllib.pathname2url(path))[0] == 'audio/mpeg'


def get_media_file(query_params):
	'''
	@param query_params: 
	'''
	id = parse_query_params(query_params, 'id', 1)
	track = db.Session.query(Audiofile).filter_by(id=id).first()
	return {'result' : track.get_properties_as_dict()}

def set_rating(query_params):
	'''
	@param query_params: 
	'''
	id = parse_query_params(query_params, 'id', 1)
	rating = int(parse_query_params(query_params, 'rating', 1))
	return

def get_library(query_params):
	'''
	@param query_params: 
	'''
	tracks = db.Session.query(Audiofile).all()
	return {'result' : [track.get_properties_as_dict() for track in tracks]}


def get_files_by_artist(query_params):
	'''
	func.lower(my_table.c.my_column).like(my_variable.lower())
	@param query_params: 
	'''
	artist = parse_query_params(query_params, 'artist', 1)
	tracks = db.Session.query(Audiofile).filter_by(tpe1 = artist)
	return {'result' : [track.get_properties_as_dict() for track in tracks]}

def get_artists(query_params):
	query = db.Session.query(Audiofile.tpe1)
	interprets = unique([interpret[0] for interpret in query])
	return {'result' : interprets}

def get_files(query_params):
	'''
	@param query_params: 
	'''
	keyword = parse_query_params(query_params, 'keyword', 1)
	keyword = '%%%s%%' % keyword.lower()
	tracks = db.Session.query(Audiofile).filter(or_(func.lower(Audiofile.tpe1).like(keyword), func.lower(Audiofile.talb).like(keyword), func.lower(Audiofile.tit2).like(keyword), func.lower(Audiofile.id).like(keyword)))
	return {'result' : [track.get_properties_as_dict() for track in tracks]}


ACTIONS = {
	'get_media_file': get_media_file,
	'set_rating': set_rating,
	'get_library': get_library,
	'get_files_by_artist': get_files_by_artist,
	'get_files': get_files,
	'update_db' : update_db,
	'get_artists' : get_artists,
	'update_db_status' : update_db_status
}

def debug(obj):
	fd = open('/tmp/foo.txt', 'a')
	fd.write('%s\n' % str(obj))
	fd.close()
	
